package com.zeyu.framework.modules.common.openid.provider;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.brickred.socialauth.AbstractProvider;
import org.brickred.socialauth.AuthProvider;
import org.brickred.socialauth.Contact;
import org.brickred.socialauth.Permission;
import org.brickred.socialauth.Profile;
import org.brickred.socialauth.exception.SocialAuthException;
import org.brickred.socialauth.exception.UserDeniedPermissionException;
import org.brickred.socialauth.oauthstrategy.OAuth2;
import org.brickred.socialauth.oauthstrategy.OAuthStrategyBase;
import org.brickred.socialauth.util.AccessGrant;
import org.brickred.socialauth.util.Constants;
import org.brickred.socialauth.util.MethodType;
import org.brickred.socialauth.util.OAuthConfig;
import org.brickred.socialauth.util.Response;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * WeiBo第三方登录服务提供者
 * Created by zeyuphoenix on 2017/1/5.
 */
public class WeiboAuthProvider extends AbstractProvider implements AuthProvider, Serializable {

    // ================================================================
    // Constants
    // ================================================================

    /**
     * uid
     */
    private static final long serialVersionUID = 1L;
    /**
     * logger
     */
    private static final Logger logger = LoggerFactory.getLogger(WeiboAuthProvider.class);

    // auth url
    private static final String GET_UID_URL = "https://api.weibo.com/2/account/get_uid.json";
    private static final String PROFILE_URL = "https://api.weibo.com/2/users/show.json";

    // set this to the list of extended permissions you want
    // Sina Weibo doesn't support scope yet.
    private static final String AllPerms = "";
    private static final String AuthenticateOnlyPerms = "";

    // end point
    private static final Map<String, String> ENDPOINTS;

    static {
        ENDPOINTS = Maps.newHashMap();
        ENDPOINTS.put(Constants.OAUTH_AUTHORIZATION_URL,
                "https://api.weibo.com/oauth2/authorize");
        ENDPOINTS.put(Constants.OAUTH_ACCESS_TOKEN_URL,
                "https://api.weibo.com/oauth2/access_token");
    }

    // ================================================================
    // Fields
    // ================================================================

    // params
    private Permission scope;
    private OAuthConfig config;
    private Profile userProfile;
    private AccessGrant accessGrant;
    private OAuthStrategyBase authenticationStrategy;

    // ================================================================
    // Constructors
    // ================================================================

    /**
     * Stores configuration for the provider
     *
     * @param providerConfig
     *            It contains the configuration of application like consumer key
     *            and consumer secret
     * @throws Exception
     */
    public WeiboAuthProvider(final OAuthConfig providerConfig) throws Exception {
        config = providerConfig;
        if (config.getCustomPermissions() != null) {
            this.scope = Permission.CUSTOM;
        }

        if (config.getAuthenticationUrl() != null) {
            ENDPOINTS.put(Constants.OAUTH_AUTHORIZATION_URL,
                    config.getAuthenticationUrl());
        } else {
            config.setAuthenticationUrl(ENDPOINTS
                    .get(Constants.OAUTH_AUTHORIZATION_URL));
        }

        if (config.getAccessTokenUrl() != null) {
            ENDPOINTS.put(Constants.OAUTH_ACCESS_TOKEN_URL,
                    config.getAccessTokenUrl());
        } else {
            config.setAccessTokenUrl(ENDPOINTS
                    .get(Constants.OAUTH_ACCESS_TOKEN_URL));
        }

        authenticationStrategy = new OAuth2(config, ENDPOINTS);
        authenticationStrategy.setPermission(scope);
        authenticationStrategy.setScope(getScope());
    }

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    /**
     * Stores access grant for the provider
     *
     * @param accessGrant
     *            It contains the access token and other information
     */
    @Override
    public void setAccessGrant(final AccessGrant accessGrant) {
        this.accessGrant = accessGrant;
        scope = accessGrant.getPermission();
        authenticationStrategy.setAccessGrant(accessGrant);
    }

    /**
     * This is the most important action. It redirects the browser to an
     * appropriate URL which will be used for authentication with the provider
     * that has been set using setId()
     *
     * @throws Exception
     */
    @Override
    public String getLoginRedirectURL(final String successUrl) throws Exception {
        return authenticationStrategy.getLoginRedirectURL(successUrl);
    }

    @Override
    public Response uploadImage(final String message, final String fileName,
                                final InputStream inputStream) throws Exception {
        logger.warn("WARNING: Not implemented for GitHub");
        throw new SocialAuthException(
                "Upload Image is not implemented for GitHub");
    }

    @Override
    protected List<String> getPluginsList() {
        List<String> list = Lists.newArrayList();
        if (config.getRegisteredPlugins() != null
                && config.getRegisteredPlugins().length > 0) {
            list.addAll(Arrays.asList(config.getRegisteredPlugins()));
        }
        return list;
    }

    @Override
    protected OAuthStrategyBase getOauthStrategy() {
        return authenticationStrategy;
    }

    /**
     * Verifies the user when the external provider redirects back to our
     * application.
     *
     *
     * @param requestParams
     *            request parameters, received from the provider
     * @return Profile object containing the profile information
     * @throws Exception
     */
    @Override
    public Profile verifyResponse(final Map<String, String> requestParams)
            throws Exception {
        return doVerifyResponse(requestParams);
    }

    /**
     * Gets the list of contacts of the user and their email.
     *
     * @return List of profile objects representing Contacts. Only name and
     *         email will be available
     * @throws Exception
     */
    @Override
    public List<Contact> getContactList() throws Exception {
        logger.warn("WARNING: Not implemented for Weibo");
        throw new SocialAuthException(
                "Get contact list is not implemented for Weibo");
    }

    /**
     * Updates the status on the chosen provider if available. This may not be
     * implemented for all providers.
     *
     * @param msg
     *            Message to be shown as user's status
     * @throws Exception
     */
    @Override
    public Response updateStatus(final String msg) throws Exception {
        CloseableHttpClient hc = HttpClientBuilder.create().build();
        try {
            HttpPost http_post = new HttpPost(
                    "https://api.weibo.com/2/statuses/update.json");

            List<NameValuePair> formparams = Lists.newArrayList();
            formparams.add(new BasicNameValuePair("access_token", accessGrant
                    .getKey()));
            formparams.add(new BasicNameValuePair("status", msg));

            http_post.setEntity(new UrlEncodedFormEntity(formparams, "UTF-8"));
            hc.execute(http_post);
        } catch (Exception e) {
            logger.error("update status error: ", e);
        } finally {
            try {
                hc.close();
            } catch (Exception ignored) {
            }
        }
        return null;
    }

    /**
     * Logout
     */
    @Override
    public void logout() {
        accessGrant = null;
        authenticationStrategy.logout();
    }

    /**
     *
     * @param p
     *            Permission object which can be Permission.AUHTHENTICATE_ONLY,
     *            Permission.ALL, Permission.DEFAULT
     */
    @Override
    public void setPermission(final Permission p) {
        logger.debug("Permission requested : " + p.toString());
        this.scope = p;
        authenticationStrategy.setPermission(this.scope);
        authenticationStrategy.setScope(getScope());
    }

    /**
     * Makes HTTP request to a given URL.
     *
     * @param url
     *            URL to make HTTP request.
     * @param methodType
     *            Method type can be GET, POST or PUT
     * @param params
     *            Any additional parameters whose signature need to compute.
     *            Only used in case of "POST" and "PUT" method type.
     * @param headerParams
     *            Any additional parameters need to pass as Header Parameters
     * @param body
     *            Request Body
     * @return Response object
     * @throws Exception
     */
    @Override
    public Response api(final String url, final String methodType,
                        final Map<String, String> params,
                        final Map<String, String> headerParams, final String body)
            throws Exception {
        logger.debug("Calling Weibo URL : " + url);
        Response serviceResponse;
        try {
            serviceResponse = authenticationStrategy.executeFeed(url,
                    methodType, params, headerParams, body);
        } catch (Exception e) {
            throw new SocialAuthException(
                    "Error while making request to URL : " + url, e);
        }
        if (serviceResponse.getStatus() != 200) {
            logger.debug("Return status for URL " + url + " is "
                    + serviceResponse.getStatus());
            throw new SocialAuthException("Error while making request to URL :"
                    + url + "Status : " + serviceResponse.getStatus());
        }
        return serviceResponse;
    }

    /**
     * Retrieves the user profile.
     *
     * @return Profile object containing the profile information.
     */
    @Override
    public Profile getUserProfile() throws Exception {
        if (userProfile == null && accessGrant != null) {
            getProfile();
        }
        return userProfile;
    }

    @Override
    public AccessGrant getAccessGrant() {
        return accessGrant;
    }

    @Override
    public String getProviderId() {
        return config.getId();
    }

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================

    private Profile doVerifyResponse(final Map<String, String> requestParams)
            throws Exception {
        logger.info("Retrieving Access Token in verify response function");
        if (requestParams.get("error_reason") != null
                && "user_denied".equals(requestParams.get("error_reason"))) {
            throw new UserDeniedPermissionException();
        }
        accessGrant = authenticationStrategy.verifyResponse(requestParams,
                MethodType.POST.toString());

        if (accessGrant != null) {
            logger.debug("Obtaining user profile");
            return getProfile();
        } else {
            throw new SocialAuthException("Access token not found");
        }
    }

    private Profile getProfile() throws Exception {
        Profile p = new Profile();
        Response serviceResponse;
        String uid = getUid();
        StringBuilder sb = new StringBuilder(PROFILE_URL);
        sb.append("?uid=").append(uid);
        try {
            serviceResponse = authenticationStrategy.executeFeed(sb.toString());
        } catch (Exception e) {
            throw new SocialAuthException(
                    "Failed to retrieve the user profile   " + PROFILE_URL, e);
        }

        String result;
        try {
            result = serviceResponse
                    .getResponseBodyAsString(Constants.ENCODING);
            logger.debug("User Profile :" + result);
        } catch (Exception e) {
            throw new SocialAuthException("Failed to read response from  "
                    + PROFILE_URL, e);
        }
        try {
            JSONObject resp = new JSONObject(result);
            if (resp.has("id")) {
                p.setValidatedId(resp.getString("id"));
                p.setEmail(resp.getString("id"));
            }
            if (resp.has("screen_name")) {
                p.setDisplayName(resp.getString("screen_name"));
                p.setLastName(resp.getString("screen_name"));
            }
            if (resp.has("name")) {
                p.setFullName(resp.getString("name"));
            }
            if (resp.has("url")) {
                p.setLocation(resp.getString("url"));
            }
            if (resp.has("gender")) {
                if ("f".equals(resp.getString("gender")))
                    p.setGender("female");
                else
                    p.setGender("male");
            }
            if (resp.has("avatar_large")) {
                p.setProfileImageURL(resp.getString("avatar_large"));
            }
            serviceResponse.close();
            p.setProviderId(getProviderId());
            userProfile = p;
            return p;
        } catch (Exception e) {
            throw new SocialAuthException(
                    "Failed to parse the user profile json : " + result, e);
        }
    }

    private String getUid() throws Exception {
        Response serviceResponse;
        try {
            serviceResponse = authenticationStrategy.executeFeed(GET_UID_URL);
        } catch (Exception e) {
            throw new SocialAuthException("Failed to retrieve the uid from  "
                    + GET_UID_URL, e);
        }

        String result;
        try {
            result = serviceResponse
                    .getResponseBodyAsString(Constants.ENCODING);
            logger.debug("Uid :" + result);
        } catch (Exception e) {
            throw new SocialAuthException("Failed to read response from  "
                    + GET_UID_URL, e);
        }
        try {
            JSONObject resp = new JSONObject(result);
            serviceResponse.close();
            return resp.getString("uid");
        } catch (Exception e) {
            throw new SocialAuthException("Failed to parse the get_uid json : "
                    + result, e);
        }
    }

    private String getScope() {
        String scopeStr;
        if (Permission.AUTHENTICATE_ONLY.equals(scope)) {
            scopeStr = AuthenticateOnlyPerms;
        } else if (Permission.CUSTOM.equals(scope)) {
            scopeStr = config.getCustomPermissions();
        } else {
            scopeStr = AllPerms;
        }
        return scopeStr;
    }

    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    // ================================================================
    // Test Methods
    // ================================================================

}
